#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""Generates site pages when requested for the first time.

This module has a base class PageGenerator. This class provides an abstract
method to generate a site page. It is extended by classes LandingPageGenerator
and LevelPageGenerator. These classes override method to generate landing pages
and level pages respectively.
"""

import logging
import os
from django.utils import simplejson
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
import db_utils
import constants
import page_tree_utils
import digital_content_wizard_exceptions

TRIX_COLUMNS = ['col' + x for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
ID_COLUMN = 'colA'
PDF_LOCATION = '/data/'
PAGE_PROPERTIES_COLUMN_START = 5  # Column F


class PageGenerator(object):
  """Base class for generating pages.

  It provides generic methods required during page generation.
  """

  def GetSiteURL(self, url):
    """Return the current site url.

    Args:
      url: The url string from which to extract the site url string.
    Returns:
      The site url string.
    """
    slash = url.rindex('/')
    return url[0:slash]

  def GetURL(self, url):
    """Return requested url.

    Args:
      url: The url string from which to extract the url string.
    Returns:
      The page url string.
    """
    slash = url.rindex('/')
    return url[slash + 1:]

  def GenerateHtml(self, template_name, template_values):
    """Renders html template with the given values.

    Args:
      template_name: String indicating the html template to render.
      template_values: A map of key/value pairs used to render the template.
    Returns:
      The rendered html content.
    """
    directory = os.path.dirname(__file__)
    path = os.path.join(directory, os.path.join('templates', template_name))
    content = template.render(path, template_values)
    return content

  def GeneratePage(self, url, version_id, ancestors):
    """Abstract method for generating page.

    Args:
      url: String representing url to the page to be generated.
      version_id: Version id number to be published.
      ancestors: Array of dictionary objects representing ancestors of the node
      in page tree, including the node itself, for which landing page is to be
      generated.
    Returns:
      html for the page. A string.
    """
    pass

  def ReadGlobalMetadata(self, version_id, template_values):
    """Fetch the fields for global metadata from the db.

    Args:
      version_id: Version id number to be published.
      template_values: Dictionary object used as out parameter to return global
      data values.
    """
    db_handler = db_utils.DbHandler()
    global_data_set = db_handler.GetGlobalDataByVersion(version_id)
    if global_data_set:
      for global_data in global_data_set:
        value_unicode = global_data.value.encode(constants.ENCODING)
        value_with_line_break = value_unicode.replace('\n', '<br>')
        template_values[global_data.variable] = value_with_line_break
    else:
      logging.error('No global data found')
    template_values['img_path'] = constants.IMAGE_PATH

  def GenerateSubHtmls(self, template_values):
    """Generates HTML snippets for parts of HTML page.

    This method generates generic HTML snippets present on all the pages. It
    generates header, footer, sidebar snippets and populates templatE_values
    accordingly.

    Args:
      template_values: Dictionary object that stores the required strings to
      detemplatize the django template.
    """
    template_values['header_html'] = self.GenerateHtml('header.html',
                                                       template_values)
    template_values['footer_html'] = self.GenerateHtml('footer.html',
                                                       template_values)
    template_values['sidebar_html'] = self.GenerateHtml('sidebar.html',
                                                        template_values)

  def SetPageProperties(self, template_values):
    """Sets values for page properties in template_values.

    constants.PAGE_PROPERTIES defines the page properties like content_type.
    This method assigns appropriate values to these properties
    in template_values dictionary object. This object is eventually passed to
    django html template.

    Args:
      template_values: Dictionary object.
    """
    i = PAGE_PROPERTIES_COLUMN_START
    for page_property in constants.PAGE_PROPERTIES:
      value = ''
      column = TRIX_COLUMNS[i] + '_value'
      if column in template_values and template_values[column]:
        value = template_values[column]
      elif page_property in template_values:
        continue
      i += 1
      template_values[page_property] = value


class LandingPageGenerator(PageGenerator):
  """Generates landing page."""

  def GenerateMetadataBlob(self, header_values, display_flag_values,
                           field_values):
    """Generates metadata blob to be displayed on the metadata widget.

    This method generates array of objects each being a name-value pair for the
    metadata row to be displayed on the metadata widget.

    Args:
      header_values: Array of metadata property headers.
      display_flag_values: Array of display flags.
      field_values: Array of metadata value strings.
    Returns:
      JSON object containing name-value pairs for the metadata columns.
    """
    metadata_blob = []
    logging.debug('Generating Metadata Blob')
    row = {}
    row['name'] = header_values[4].encode(constants.ENCODING)
    row['value'] = field_values[4].encode(constants.ENCODING)
    metadata_blob.append(row)
    starting_column = PAGE_PROPERTIES_COLUMN_START + len(
        constants.PAGE_PROPERTIES)
    for i in range(starting_column, len(header_values)):
      display_flag = constants.DISPLAY_FLAGS[display_flag_values[i]]
      if display_flag & 2:
        row = {}
        row['name'] = header_values[i].encode(constants.ENCODING)
        if len(field_values) > i:
          row['value'] = field_values[i].encode(constants.ENCODING)
        else:
          row['value'] = ''.encode(constants.ENCODING)
        metadata_blob.append(row)
    return metadata_blob

  def GetBreadCrumbData(self, field_values, levels):
    """Returns array of name-url map for breadcrumb data.

    Args:
      field_values: Array of metadata values.
      levels: Number of page tree levels.
    Returns:
      Array of dictionary objects having name-url pairs.
    """
    breadcrumb_data = []
    breadcrumb_url = ''
    for i in range(0, levels):
      if i:
        breadcrumb_url += '_'
      breadcrumb_url += field_values[i + 1].replace(' ', '_')
      breadcrumb_data.append({'name': field_values[i + 1],
                              'url': breadcrumb_url + '.html'})
    return breadcrumb_data

  def GenerateSubHtmlsForLandingPage(self, template_values, header_values,
                                     display_flag_values, field_values):
    """Generates HTML snippets for parts of landing page.

    This method generates HTML snippets for parts of landing page like
    breadcrumb, metadata blob, slider, header, footer, sidebar and populates
    template_values accordingly.

    Args:
      template_values: Dictionary object that stores the required string to
      detemplatize the django template.
      header_values: Array of strings representing column titles.
      display_flag_values: Array of display flag values.
      field_values: Array of column value strings.
    """
    self.GenerateSubHtmls(template_values)
    self.encoded_url_string = 'url'.encode(constants.ENCODING)
    self.encoded_name_string = 'name'.encode(constants.ENCODING)
    breadcrumb_data = self.GetBreadCrumbData(field_values,
                                             template_values['noOfLevels'])
    template_values['breadcrumb'] = self.GenerateHtml(
        'breadcrumb.html', {'breadcrumb_data': breadcrumb_data})
    metadata_values = {}
    metadata_values['metadata_content'] = self.GenerateMetadataBlob(
        header_values, display_flag_values, field_values)
    template_values['metadata_blob'] = self.GenerateHtml(
        'metadata_blob.html', metadata_values)
    template_values['slider_html'] = self.GenerateHtml('slider.html',
                                                       template_values)
    template_values['cse_box'] = self.GenerateHtml('cse_box.html',
                                                   template_values)
    template_values['sharelink'] = self.GenerateHtml('sharelink.html',
                                                     template_values)

  def GeneratePage(self, url, version_id, ancestors):
    """Generates landing page.

    Args:
      url: String representing url to the page to be generated.
      version_id: Version id number to be published.
      ancestors: Array of dictionary objects representing ancestors of the node
      in page tree, including the node itself, for which landing page is to be
      generated. (In case of landing pages, this argument is not used as
      traversing the page tree for landing page is more expensive than fetching
      the row from the metadata).
    Returns:
      html for the landing page. A string.
    """
    # Initialize.
    title_column = None
    description_column = None
    keywords_column = None
    template_values = {}
    db_handler = db_utils.DbHandler()

    # Fetch global data.
    self.ReadGlobalMetadata(version_id, template_values)
    template_values['site_url'] = self.GetSiteURL(url)
    url = self.GetURL(url)
    page_id = url.split('.')[0]
    template_values['noOfLevels'] = db_handler.GetNumberOfLevels(version_id)
    page_tree_utils_instance = page_tree_utils.GQLUtils(version_id)
    # Fetch header and display flag values.
    header_values = page_tree_utils_instance.GetMetadataRowByNumber(0)
    display_flag_values = page_tree_utils_instance.GetMetadataRowByNumber(-1)
    for column_index, field in enumerate(header_values):
      template_values[TRIX_COLUMNS[column_index] + '_header_name'] = field
      if field == 'Title':
        title_column = TRIX_COLUMNS[column_index]
      if field == 'Description':
        description_column = TRIX_COLUMNS[column_index]
      if field == 'Keywords':
        keywords_column = TRIX_COLUMNS[column_index]

    template_values['version_id'] = str(version_id)
    logging.debug('id = ' + page_id)
    field_values = db_handler.GetMetadataById(version_id, page_id)
    for column_index, field in enumerate(field_values):
      template_values[TRIX_COLUMNS[column_index] + '_value'] = field
      # Generate url for landing page (<id>.html).
    # Set the page properties content_type.
    self.SetPageProperties(template_values)
    template_values['colA_value'] = page_id
    template_values['colB_url'] = template_values.get('colB_value').replace(
        ' ', '_') + '.html'
    template_values['colC_url'] = template_values.get('colB_url').replace(
        '.html', '') + '_' + template_values.get('colC_value').replace(
            ' ', '_') + '.html'
    template_values['colD_url'] = template_values.get('colC_url').replace(
        '.html', '') + '_' + template_values.get('colD_value').replace(
            ' ', '_') + '.html'
    self.GenerateSubHtmlsForLandingPage(template_values, header_values,
                                        display_flag_values, field_values)
    template_values['page_title'] = template_values['html_title']
    if title_column:
      template_values['page_title'] = template_values.get(
          title_column + '_value')
    if description_column:
      template_values['page_description'] = template_values.get(
          description_column + '_value')
    if keywords_column:
      template_values['page_keywords'] = template_values.get(
          keywords_column + '_value')
    if page_id:
      page_map = {}

      template_dict = {
          constants.CONTENT_TYPES[0] : 'landing_page_pdf.html',
          constants.CONTENT_TYPES[1] : 'landing_page_video.html',
          constants.CONTENT_TYPES[2] : 'landing_page_photo.html',
          constants.CONTENT_TYPES[3] : 'landing_page_doc.html',
          constants.CONTENT_TYPES[4] : 'landing_page_presentation.html',
          constants.CONTENT_TYPES[5] : 'landing_page_spreadsheet.html'
          }

      if template_values['content_type'] in template_dict:
        logging.debug('Generating content page using template: ' +
                      template_dict[template_values['content_type']])
        page_map[url] = self.GenerateHtml(
            template_dict[template_values['content_type']], template_values)
      else:
        logging.error('Unknown content type')
        return ''
      try:
        db_handler.AddPages(page_map, version_id)
      except digital_content_wizard_exceptions.DbException, e:
        logging.error(e)
      finally:
        return page_map[url]


class LevelPageGenerator(PageGenerator):
  """Generates level page."""

  def __init__(self, page_tree):
    self.page_tree = page_tree

  def GetBreadCrumbData(self):
    """Returns array of name-url map for breadcrumb data.

    Returns:
      Array of dictionary objects having name-url pairs.
    """
    breadcrumb_data = []
    breadcrumb_nodes = self.ancestors[:-1]
    breadcrumb_nodes.reverse()
    for breadcrumb_node in breadcrumb_nodes:
      breadcrumb_data.append(
          {'name': breadcrumb_node[self.encoded_name_string],
           'url': breadcrumb_node[self.encoded_url_string]})
    return breadcrumb_data

  def GetDisplayHeaders(self, header_values, display_flag_values, level):
    """Returns array of display headers that need to be present on the page.

    Args:
      header_values: Array of strings representing column titles.
      display_flag_values: Array of display flag values.
      level: Number representing the level of current page.
    Returns:
      Array of header strings to be present on the metadata table.
    """
    display_headers = []
    display_headers.append(header_values[4])
    current = level + 1
    while current <= self.no_of_levels:
      display_headers.append(header_values[current])
      current += 1
    starting_column = PAGE_PROPERTIES_COLUMN_START + len(
        constants.PAGE_PROPERTIES)
    for i in range(starting_column, len(header_values)):
      display_flag = constants.DISPLAY_FLAGS[display_flag_values[i]]
      if display_flag & 1:
        display_headers.append(header_values[i])
    return display_headers

  def GenerateTableRow(self, node):
    """Generates table row for the specified leaf node.

    Args:
      node: Dictionary object representing the page tree leaf node.
    """
    table_row = {}
    table_row['column_values'] = []
    field_values = node[self.encoded_name_string].split('\t')
    title = field_values[0]
    field_values = field_values[1:]
    table_row['column_values'].append({'name': title,
                                       'url': node[self.encoded_url_string]})
    for ancestor in self.ancestor_nodes[1:]:
      table_row['column_values'].append(
          {'name': ancestor[self.encoded_name_string],
           'url': ancestor[self.encoded_url_string]})
    for value in field_values:
      table_row['column_values'].append({'name': value})
    self.table_data['rows'].append(table_row)

  def GenerateTableRows(self, node):
    """Browses through the page tree and for all leaf nodes generate table row.

    Args:
      node: Dictionary object representing the page tree node.
    """
    if self.encoded_child_nodes_string not in node:
      # Leaf page. Update table_data
      self.GenerateTableRow(node)
      return
    child_nodes = node[self.encoded_child_nodes_string]
    for child_node in child_nodes:
      self.ancestor_nodes.append(node)
      self.GenerateTableRows(child_node)
      self.ancestor_nodes = self.ancestor_nodes[:-1]

  def GenerateLevelTable(self, version_id, level):
    """Generate table data to be displayed on the intermediate pages.

    This method accepts node key and level as input. It generates for the
    specified node of the specified level, table data to be displayed on the
    intermediate page. This table data consists of one row per leaf page, which
    is descendent of this node in the page tree. Each row consists of columns
    which are configured to be displayed on the page table.

    Args:
      version_id: Version number.
      level: Number indicating the page tree level of the specified node.
    Returns:
      table_data: JSON object containing table data.
    """
    self.table_data = {}
    self.table_data['rows'] = []
    page_tree_utils_instance = page_tree_utils.GQLUtils(version_id)
    # Fetch header and display flag values.
    header_values = page_tree_utils_instance.GetMetadataRowByNumber(0)
    self.display_flag_values = page_tree_utils_instance.GetMetadataRowByNumber(
        -1)
    self.table_data['headers'] = self.GetDisplayHeaders(
        header_values, self.display_flag_values, level)
    self.ancestor_nodes = []
    self.GenerateTableRows(self.node)

  def GenerateSubHtmlsForLevelPage(self, template_values, version_id,
                                   level):
    """Generates HTML snippets for parts of intermediate  page.

    This method generates HTML snippets for parts of intermediate page like
    breadcrumb, metadata table, header, footer, sidebar and populates
    template_values accordingly.

    Args:
      template_values: Dictionary object that stores the required string to
      detemplatize the django template.
      version_id: Version number.
      level: Number indicating the level of this intermediate page.
    """
    self.GenerateSubHtmls(template_values)
    breadcrumb_data = self.GetBreadCrumbData()
    template_values['breadcrumb'] = self.GenerateHtml(
        'breadcrumb.html', {'breadcrumb_data': breadcrumb_data})
    metadata_table_values = {}
    self.GenerateLevelTable(version_id, level)
    metadata_table_values['table_data'] = self.table_data
    template_values['metadata_table'] = self.GenerateHtml(
        'metadata_table.html', metadata_table_values)
    template_values['cse_box'] = self.GenerateHtml('cse_box.html',
                                                   template_values)

  def GeneratePage(self, url, version_id, ancestors):
    """Generates intermediate level page.

    Args:
      url: String representing url to the page to be generated.
      version_id: Version number to be published.
      ancestors: Array of dictionary objects representing ancestors of the node
      in page tree, including the node itself, for which landing page is to be
      generated.
    Returns:
      html for the landing page. A string.
    """
    template_values = {}
    self.node = ancestors[0]
    self.ancestors = ancestors
    url = self.GetURL(url)
    db_handler = db_utils.DbHandler()
    self.ReadGlobalMetadata(version_id, template_values)
    template_values['version_id'] = str(version_id)
    self.no_of_levels = db_handler.GetNumberOfLevels(version_id)
    template_values['noOfLevels'] = self.no_of_levels
    level = len(self.ancestors) - 1
    self.encoded_url_string = 'url'.encode(constants.ENCODING)
    self.encoded_name_string = 'name'.encode(constants.ENCODING)
    self.encoded_child_nodes_string = 'childNodes'.encode(constants.ENCODING)
    template_values['colD_name'] = self.node[self.encoded_name_string]
    self.GenerateSubHtmlsForLevelPage(template_values, version_id, level)
    self.ancestors.reverse()
    i = 1
    for ancestor in self.ancestors[1:]:
      key = TRIX_COLUMNS[i] + '_value'
      template_values[key] = ancestor[self.encoded_name_string]
      i += 1
    page_map = {}
    logging.debug('URL:')
    logging.debug(url)
    page_map[url] = self.GenerateHtml('level3_page.html', template_values)
    try:
      db_handler.AddPages(page_map, version_id)
    except digital_content_wizard_exceptions.DbException, e:
      logging.error(e)
    finally:
      return page_map[url]
